﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Net.Sockets;
using System.Threading;
using Antix.Mail.Smtp.Impostor;
using Xunit;

namespace Antix.Mail.Tests
{
    /// <summary>
    ///   <para>Tests for Impostor</para>
    /// </summary>
    public class ImpostorTests
    {
        const string IPADDRESS = "0.0.0.0"; // Any
        const int PORT = 25;
        const int MESSAGE_COUNT = 10;
        const string SUBJECT = "Test E-mail Æ Ø Å";
        const string BODY = "This is a test";
        const string FROM_EMAIL = "from@localhost";
        const string FROM_NAME = "From Mail Åddress";
        const string TO_EMAIL = "to@localhost";
        const string TO_NAME = "To Mail Address";
        const string TO2_EMAIL = "to2@localhost";
        const string TO2_NAME = "To 2 Mail Address";

        public ImpostorTests()
        {
            // open the temp path
            try
            {
                Process.Start(Path.GetTempPath());
            }
            // ReSharper disable EmptyGeneralCatchClause
            // ignore errors here, not testing this
            catch (Exception)
            {
                // ReSharper restore EmptyGeneralCatchClause
            }
        }

        [Fact]
        [Description("Start, stop and dispose of a host")]
        public void StartStopDisposeHost()
        {
            var ipAddress = IPAddress.Parse(IPADDRESS);

            var server = new Server();
            try
            {
                var host = server.CreateHost(new HostConfiguration
                {
                    IPAddress = ipAddress,
                    Port = PORT
                });
                host.Messages.DeleteAll();

                Assert.Equal(1, server.Hosts.Count);
                Assert.NotNull(server.Hosts.First());
                Assert.Equal(ipAddress, server.Hosts.First().IPAddress);
                Assert.Equal(PORT, server.Hosts.First().Port);

                host.Start();
                Assert.Equal(HostStates.Started, host.Status);

                SendEmails(server, 1);

                host.Stop();
                Assert.Equal(HostStates.Stopped, host.Status);

                host.Start();
                Assert.Equal(HostStates.Started, host.Status);

                SendEmails(server, 1);

                host.Dispose();
                Assert.Equal(HostStates.Stopped, host.Status);
            }
            finally
            {
                server.Dispose();
            }
        }

        [Fact]
        [Description("Create duplicate host")]
        public void CreateDuplicateHost()
        {
            var server = new Server();

            try
            {
                var host = server.CreateHost(new HostConfiguration {Port = PORT});
                host.Start();

                host = server.CreateHost(new HostConfiguration {Port = PORT});
                host.Start();

                Assert.True(false, "Created duplicate host");
            }
            catch (Exception ex)
            {
                Assert.IsType<SocketException>(ex);
            }
            finally
            {
                server.Dispose();
            }
        }

        [Fact]
        [Description("Create a host and send e-mail to it from a number of threads")]
        public void CreateHostSendEmail_Threads()
        {
            var server = new Server();
            var threads = new List<Thread>();
            try
            {
                var host = server.CreateHost(new HostConfiguration {Port = PORT});
                host.Messages.DeleteAll();
                host.Start();

                var startedOn = DateTime.Now;

                for (var i = 0; i < 50; i++)
                {
                    Trace.TraceInformation("sending on thread {0}", i);

                    var thread = new Thread(SendEmails);
                    thread.Start(server);

                    threads.Add(thread);
                }

                foreach (var thread in threads)
                {
                    thread.Join();
                }

                Trace.TraceInformation("Done in {0}", DateTime.Now - startedOn);
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("Antix.Mail.Tests", ex.ToString(), EventLogEntryType.Error);
            }
            finally
            {
                server.Dispose();

                Trace.TraceInformation("Finally");
            }
        }

        [Fact]
        [Description("Create a host and send e-mail to it")]
        public void CreateHostSendEmail()
        {
            var server = new Server();
            try
            {
                var host = server.CreateHost(new HostConfiguration
                {
                    IPAddress = IPAddress.Parse(IPADDRESS),
                    Port = PORT
                });
                host.Messages.DeleteAll();
                host.Start();

                SendEmails(server);

                Assert.NotNull(host.Messages.FirstOrDefault());
            }
            finally
            {
                server.Dispose();
            }
        }

        [Fact]
        [Description("Create a host start and stop and send e-mail to it")]
        public void CreateHostSendEmail_Stopped()
        {
            var server = new Server();
            try
            {
                var host = server.CreateHost(new HostConfiguration {Port = PORT});
                host.Messages.DeleteAll();
                host.Start();
                host.Stop();

                // send a _message
                var message = new MailMessage
                {
                    From = new MailAddress(FROM_EMAIL, FROM_NAME),
                    Subject = SUBJECT,
                    Body = BODY
                };
                message.To.Add(new MailAddress(TO_EMAIL, TO_NAME));

                var smtpClient = new SmtpClient(host.DNSHostEntry.HostName, host.Port);
                smtpClient.Send(message);

                Assert.True(false, "shouldn't get here");
            }
            catch (SmtpException ex)
            {
                Assert.NotNull(ex.InnerException);
                Assert.IsType<WebException>(ex.InnerException);
                Assert.NotNull(ex.InnerException.InnerException);
                Assert.IsType<SocketException>(ex.InnerException.InnerException);

                var socketException = (SocketException) ex.InnerException.InnerException;
                Assert.Equal(10061 /* Connection Refused */, socketException.ErrorCode);
            }
            finally
            {
                server.Dispose();
            }
        }

        #region send e-mails

        public void SendEmails(object serverObject)
        {
            try
            {
                SendEmails((Server) serverObject, MESSAGE_COUNT);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
                throw;
            }
        }

        public static void SendEmails(Server server, int sendCount)
        {
            var host = server.Hosts.First();
            Assert.Equal(HostStates.Started, host.Status);

            // send a _message
            var message = new MailMessage
            {
                From = new MailAddress(FROM_EMAIL, FROM_NAME),
                Subject = SUBJECT,
                Body = BODY
            };
            message.To.Add(new MailAddress(TO_EMAIL, TO_NAME));
            message.To.Add(new MailAddress(TO2_EMAIL, TO2_NAME));

            // add an attachment
            var resource = typeof(ImpostorTests).Assembly
                .GetManifestResourceStream("Antix.Mail.Tests.Image.png");
            if (resource == null)
                Assert.True(false, "Could not load attachment resource 'Antix.Mail.Tests.Image.png'");
            message.Attachments.Add(new Attachment(resource, "image.png"));

            var smtpClient = new SmtpClient(
                host.DNSHostEntry.HostName, // works, but, handled exception thrown (on my network anyway), ipv4 address works fine
                host.Port);

            for (var i = 0; i < sendCount; i++)
            {
                message.Subject = SUBJECT;

                smtpClient.Send(message);
                Console.WriteLine($"sent email {i}");
            }

            smtpClient.Dispose();

            // check messages
            foreach (var receivedMessage in host.Messages)
            {
                var from = receivedMessage.From.ToMailAddress();
                var to = receivedMessage.To.ToMailAddresses().ToArray();

                Assert.NotNull(receivedMessage);
                Assert.Equal(new MailAddress(FROM_EMAIL, FROM_NAME), from);
                Assert.Equal(SUBJECT, receivedMessage.Subject);
                Assert.Equal(2, to.Count());
                Assert.Equal(new MailAddress(TO_EMAIL, TO_NAME), to.ElementAt(0));
                Assert.Equal(new MailAddress(TO2_EMAIL, TO2_NAME), to.ElementAt(1));
                //Assert.NotNull(receivedMessage.Data);

                Console.WriteLine($"check email {receivedMessage.Subject}");
            }
        }

        #endregion
    }
}